//
//  SSHSession.m
//  tunneltool
//
//  Created by Andreas Hindborg on 12/05/11.
//  Copyright 2011 Andreas Hindborg. All rights reserved.
//

#import "SSHSession.h"
#import <stdio.h>
#import <arpa/inet.h>
#import <sys/socket.h>
#import <netinet/in.h>

@interface SSHSession ()
@property (readwrite, retain) NSString *remoteIP;
@property (readwrite, assign) uint32_t remotePort;
@end

@implementation SSHSession

@synthesize remoteIP;
@synthesize remotePort;
@synthesize user;
@synthesize publicKeyPath;
@synthesize privateKeyPath;
@synthesize sessionSocket;
@synthesize session;
@synthesize connected;
@synthesize passwordDelegate;


- (LIBSSH2_SESSION*) getSession
{
    return session;
}


- (SSHSession*) initWithRemoteIP: (NSString*)ip remotePort: (uint32_t)port
{
    self = [super init];
    if(!self) return self;
    [self setRemoteIP: ip];
    [self setRemotePort: port];
    tunnels = [[NSMutableSet alloc] init];
    connected = false;
    return self;
}

- (int) connect
{
    struct sockaddr_in sin;
    int err;
    const char *fingerprint;
    const char *authlist;
    const char *utf8user;

    if(![self user]) {
        NSLog(@"No username set!");
        return 6;
    }

    sessionSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = inet_addr([[self remoteIP] UTF8String]);

    if(sin.sin_addr.s_addr == INADDR_NONE) {
		NSLog(@"Invalid remote host: %@", remoteIP);
        return 1;
    }

    sin.sin_port = htons([self remotePort]);

    err = connect(sessionSocket, (struct sockaddr*) &sin, sizeof(struct sockaddr_in));
    if(err) {
        NSLog(@"connect: %s", strerror(errno));
        return 2;
    }

    session = libssh2_session_init();
    if(!session) {
        NSLog(@"libssh2_init: Failed to init session!");
        return 3;
    }

    err = libssh2_session_startup(session, sessionSocket);
    if(err) {
        NSLog(@"libssh2_session_startup: Failed with error code: %d", err);
        return 4;
    }

    fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
    NSMutableString *nsprint = [[NSMutableString alloc] init];

    for (int i = 0; i < 20; i++) {
        [nsprint appendString:[NSString stringWithFormat: @"%02X", (unsigned char) fingerprint[i] ]];
    }
    NSLog(@"Server fingerprint: %@", nsprint);

    
    utf8user = [[self user] UTF8String];

    authlist = libssh2_userauth_list(session, utf8user, strlen(utf8user));
    if (publicKeyPath && privateKeyPath && strstr(authlist, "publickey")) {
        NSLog(@"Authenticating using publickey");
        err = libssh2_userauth_publickey_fromfile(session, utf8user, [[self publicKeyPath] UTF8String], [[self privateKeyPath] UTF8String], NULL);
        if (err) {
            NSLog(@"libssh2_userauth_publickey_fromfile: Failed with error code: %d", err);
            return 7;
        }
    }
    else if (passwordDelegate && strstr(authlist, "password")) {
        bool retry;
        do {
            retry = false;
            NSString *pass = [passwordDelegate passwordCallback];
            err = libssh2_userauth_password(session, utf8user, [pass UTF8String]);
            if (err == LIBSSH2_ERROR_AUTHENTICATION_FAILED) {
                NSLog(@"SSHSession: Authentication failed");
                retry = true;
            }
            else if (err) {
                NSLog(@"Password authentication not supported!");
                return 8;
            }
        } while (retry);
    }
    else {
        NSLog(@"No valid auth method. Server supports: %s", authlist);
        return 5;
    }

    NSLog(@"Connected to %@:%d", [self remoteIP], [self remotePort]);
    
    connected = true;
    return 0;
}


- (SSHTunnel*) createTunnelWithLocalIP:(NSString*) aLocalIP
                             localPort:(uint32_t) aLocalPort
                              remoteIP:(NSString*) aRemoteIP
                            remotePort: (uint32_t) aRemotePort
{
    SSHTunnel *tunnel = [[SSHTunnel alloc] initWithSession:self 
                                                  remoteIP:aRemoteIP 
                                                remotePort:aRemotePort 
                                                   localIP:aLocalIP 
                                                 localPort:aLocalPort];
    [tunnels addObject:tunnel];
	return tunnel;
}

@end
